//

(function() {

  // list of guis
  var guis = [];

  // default slider params
  var sliderMin = 0;
  var sliderMax = 100;
  var sliderStep = 1;

  // default gui provider
  var guiProvider = 'QuickSettings';

  const defaultLabel = 'p5.gui';

  // Create a GUI using QuickSettings (or DAT.GUI or ...)
  // You only need to pass a reference to the sketch in instance mode

  // Usually you will call createGui(this, 'label');
  p5.prototype.createGui = function(sketch, label, provider) {

    // createGui(label) signature
    if ((typeof sketch) === 'string') {
      return this.createGui(label, sketch, provider);
    }

    // normally the sketch will just be embedded below the body
    let parent = document.body;

    if(sketch === undefined) {
      // p5js global mode
      sketch = window;
      label = label || document.title || defaultLabel;
    } else {
      // p5js instance mode
      parent = sketch.canvas.parentElement;
      label = label || parent.id || defaultLabel;
    }

    if(!('color' in sketch)) {
      console.error(`${parent.id}: You need to pass the p5 sketch to createGui in instance mode!`);
    }

    // default gui provider
    provider = provider || guiProvider;

    var gui;

    // create a gui using the provider
    if(provider === 'QuickSettings') {
      if(QuickSettings) {
        console.log('Creating p5.gui powered by QuickSettings.');
        gui = new QSGui(label, parent, sketch);
      } else {
        console.log('QuickSettings not found. Is the script included in your HTML?');
        gui = new DummyGui(label, parent, sketch);
      }
    } else {
      console.log('Unknown GUI provider ' + provider);
      gui = new DummyGui(label, parent, sketch);
    }

    // add it to the list of guis
    guis.push(gui);

    // return it
    return gui;

  };


  p5.prototype.removeGui = function(gui) {
    // TODO: implement this
  };

  // update defaults used for creation of sliders
  p5.prototype.sliderRange = function(vmin, vmax, vstep) {
    sliderMin = vmin;
    sliderMax = vmax;
    sliderStep = vstep;
  };

  // extend default behaviour of noLoop()
  p5.prototype.noLoop = function() {
    this._loop = false;
    for(var i = 0; i < guis.length; i++) {
      guis[i].noLoop();
    }
  };

  // extend default behaviour of loop()
  p5.prototype.loop = function() {
    for(var i = 0; i < guis.length; i++) {
      guis[i].loop();
    }
    this._loop = true;
    this._draw();
  };


  // interface for quicksettings
  function QSGui(label, parent, sketch) {

    // hard code the position, it can be changed later
    let x = 20;
    let y = 20;

    var qs = QuickSettings.create(x, y, label, parent);

    // proxy all functions of quicksettings
    this.prototype = qs;

    // addGlobals(global1, global2, ...) to add the selected globals
    this.addGlobals = function() {
      qs.bindGlobals(arguments);
    };

    // addObject(object) to add all params of the object
    // addObject(object, param1, param2, ...) to add selected params
    this.addObject = function() {
      // get object
      object = arguments[0];
      // convert arguments object to array
      var params = [];
      if(arguments.length > 1) {
        params = Array.prototype.slice.call(arguments)
        params = params.slice(1);
      }
      // if no arguments are provided take all keys of the object
      if(params.length === 0) {
        // won't work in Internet Explorer < 9 (use a polyfill)
        params = Object.keys(object);
      }
      qs.bindParams(object, params);
    };

    // noLoop() to call draw every time the gui changes when we are not looping
    this.noLoop = function() {
      qs.setGlobalChangeHandler(sketch._draw);
    };

    this.loop = function() {
      qs.setGlobalChangeHandler(null);
    };

    // pass through ...
    this.show = function() { qs.show(); };
    this.hide = function() { qs.hide(); };
    this.toggleVisibility = function() { qs.toggleVisibility(); };
    this.setPosition  = function(x, y) {
      qs.setPosition(x, y);
      return this;
    };

    // Extend Quicksettings
    // so it can magically create a GUI for parameters passed by name
    qs.bindParams = function(object, params) {

      // iterate over all the arguments
      for(var i = 0; i < params.length; i++) {

        var arg = params[i];
        var val = object[arg];
        var typ = typeof val;

        //console.log(typ, arg, val);

        // don't need to show the sliders for range min, max and step of a property
        var sliderConfigRegEx = /^(.*min|.*max|.*step)$/i;
        if( sliderConfigRegEx.test(arg)){
          continue;
        }
        switch(typ) {

          case 'object':

            // color triple ?
            if(val instanceof Array && val.length === 3 && typeof val[0] === 'number') {
              // create color according to the current color mode of the current sketch
              var c = sketch.color(val[0], val[1], val[2]);
              // get decimal RGB values
              var c2 = c.levels.slice(0,3);
              // create HTML color code
              var vcolor = '#' + c2.map(function(value) {
                return ('0' + value.toString(16)).slice(-2);
              }).join('');
              this.bindColor(arg, vcolor, object);
            } else {
              // multiple choice drop down list
              this.bindDropDown(arg, val, object);
              object[arg] = val[0];
            }
            break;

          case 'number':

            // values as defined by magic variables or gui.sliderRange()
            var vmin = object[arg + 'Min'] || object[arg + 'min'] || sliderMin;
            var vmax = object[arg + 'Max'] || object[arg + 'max'] || sliderMax;
            var vstep = object[arg + 'Step'] || object[arg + 'step'] || sliderStep;

            // the actual values can still overrule the limits set by magic
            var vmin = Math.min(val, vmin);
            var vmax = Math.max(val, vmax);

            // set the range
            this.bindRange(arg, vmin, vmax, val, vstep, object);

            break;

          case 'string':

            var HEX6 = /^#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i;
            if(HEX6.test(val)) {
              // HTML color value (such as #ff0000)
              this.bindColor(arg, val, object);
            } else {
              // String value
              this.bindText(arg, val, object);
            }
            break;

          case 'boolean':

            this.bindBoolean(arg, object[arg], object);
            break;

        }
      }
    };

    // bind params that are defined globally
    qs.bindGlobals = function(params) {
      this.bindParams(window, params);
    };

  }

  // Just a Dummy object that provides the GUI interface
  function DummyGui() {
    var f = function() {};
    this.addGlobals = f;
    this.noLoop = f;
    this.addObject = f;
    this.show = f;
  }


})();
